/*
This file is part of [ahttpserver] project. 

Author: Artem Kustikov (kustikoff[at]tut.by)


This code is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this code.

Permission is granted to anyone to use this code for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:

1. The origin of this code must not be misrepresented; you must
not claim that you wrote the original code. If you use this
code in a product, an acknowledgment in the product documentation
would be appreciated but is not required.

2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original code.

3. This notice may not be removed or altered from any source
distribution.
*/

#include "aconnect/config.hpp"
#include <boost/scoped_array.hpp>
#include <boost/thread.hpp>

#include "ahttplib.hpp"
#include "aconnect/util.string.hpp"

#include "wrappers.hpp"

//////////////////////////////////////////////////////////////////////////
// 
//	
aconnect::string_constptr RequestWrapper::param (aconnect::string_constptr key)  
{
	processRequest();

	PyThreadStateGuard guard;

	aconnect::str2str_map::const_iterator iter;
	if ((iter = _context->GetParameters.find(key)) != _context->GetParameters.end())
		return iter->second.c_str();

	if ((iter = _context->PostParameters.find(key)) != _context->PostParameters.end())
		return iter->second.c_str();

	if ((iter = _context->Cookies.find(key)) != _context->Cookies.end())
		return iter->second.c_str();

	return NULL;
}


std::string RequestWrapper::read (int buffSize)	{ 

	if (_requestPostLoaded) 
		throwRequestProcessedError ();

	_requestReadInRawForm = true;

	PyThreadStateGuard stateGuard;

	boost::scoped_array<aconnect::char_type> buff (new aconnect::char_type [buffSize]);
	int bytesRead = _context->RequestStream.read (buff.get(), buffSize);

	return std::string (buff.get(), bytesRead);
}

std::string RequestWrapper::readline ()
{
	PyThreadStateGuard stateGuard;

	std::string res;
	_context->RequestStream.readLine(res);
	
	return res;
}

std::vector<std::string> RequestWrapper::readlines (int hint)
{
	PyThreadStateGuard stateGuard;
	std::vector<std::string> list;
	std::string line;

	while (_context->RequestStream.readLine(line))
		list.push_back (line);
	
	return list;
}

void RequestWrapper::processGetData() 
{
	if (_requestGetLoaded)
		return;

	PyThreadStateGuard stateGuard;
	_context->parseQueryStringParams ();
	_context->parseCookies();

	_requestGetLoaded = true;

}

void RequestWrapper::processRequest() 
{
	if (_requestReadInRawForm)
		return throwRequestReadRawError ();

	if (_requestPostLoaded)
		return;
	
	// load request data
	processGetData(); 

	PyThreadStateGuard stateGuard;
	_context->loadPostParams ();
	
	_requestPostLoaded = true;
}

//////////////////////////////////////////////////////////////////////////
void ResponseWriterWrapper::write (aconnect::string_constref data)
{
	assert (_context);
	
	PyThreadStateGuard stateGuard;
	
	_context->Response.write(data.c_str(), data.size());
}

//////////////////////////////////////////////////////////////////////////
// 
//	wrapper for ahttp::HttpContext - cover some HttpContext functionality
void HttpContextWrapper::write (aconnect::string_constref data) 
{
	assert (_context);

	PyThreadStateGuard stateGuard;

	if (!_contentWritten)
	{
		_contentWritten = true;
		_context->setHtmlResponse();
	}

	_context->Response.writeString (data);
}

void HttpContextWrapper::writeEscaped (aconnect::string_constref data) 
{
	write ( aconnect::util::escapeHtml(data).c_str() );
}

void HttpContextWrapper::flush () 
{
	PyThreadStateGuard stateGuard;

	assert (_context);

	_context->Response.flush();
}

void HttpContextWrapper::setContentType (aconnect::string_constptr contentType, aconnect::string_constptr charset) 
{
	assert (_context);
	
	if (_contentWritten)
	{
		PyErr_SetString (PyExc_RuntimeError, "HTTP response header cannot be set - response content writing already started");
		python::throw_error_already_set();
	}

	_context->Response.Header.setContentType (contentType, charset);
}

PyObject* HttpContextWrapper::startWsgiResponse (std::string status, python::list response_headers, PyObject* exc_info )
{
	assert (_context);

	if (!_context->isClientConnected())
		return 0;

	// check Exception info presence
	if (exc_info && exc_info != Py_None) 
	{
		if (_context->Response.isHeadersSent())
		{
			PyObject *type = 0;
            PyObject *value = 0;
            PyObject *traceback = 0;

            if (!PyArg_ParseTuple(exc_info, "OOO", &type, &value, &traceback))
                return 0;

            Py_INCREF(type);
            Py_INCREF(value);
            Py_INCREF(traceback);

            PyErr_Restore(type, value, traceback);
            return 0;
		}

		exc_info = Py_None;
	}

	// 'start_response' called second time
	if (_context->Response.Header.Status != ahttp::HttpResponseHeader::UnknownStatus) {
		PyErr_SetString(PyExc_RuntimeError, "HTTP headers have already been sent"); 
		return 0;

	}

	_context->Response.Header.loadCustomStatus (status.c_str());

	for (int ndx = 0; ndx < python::len(response_headers); ++ndx) 
	{
		std::string name = python::extract<std::string> (response_headers[ndx][0]);
		std::string value = python::extract<std::string> (response_headers[ndx][1]);
		
		_context->Response.Header.setHeader (name, value);
	}
	
	// with_custodian_and_ward: Ties lifetimes of the arguments
	// with_custodian_and_ward_postcall: Ties lifetimes of the arguments and results
	// return_internal_reference: Ties lifetime of one argument to that of result
	// return_value_policy<T> with T one of:
	// reference_existing_object: naive (dangerous) approach
	// copy_const_reference: Boost.Python v1 approach
	// copy_non_const_reference
	// manage_new_object

	// return 'write' callable

	python::manage_new_object::apply<ResponseWriterWrapper*>::type writeConverter;
	return writeConverter(new ResponseWriterWrapper(_context));
}

//////////////////////////////////////////////////////////////////////////
//
//	Utility
namespace PythonGlobals
{
	extern boost::mutex LoadMutex;
}

aconnect::string loadPythonError()
{
	using namespace python;
	
	aconnect::string errorDesc = "Undefined Python exception caught";
	PyObject* type = NULL, 
		*value = NULL, 
		*traceback = NULL;

	boost::mutex::scoped_lock loadLock(PythonGlobals::LoadMutex);

	try {
		
		if ( !PyErr_Occurred ())
			return errorDesc;

		PyErr_Fetch (&type, &value, &traceback);
		PyErr_NormalizeException (&type, &value, &traceback);
		
		PyErr_Clear();

		loadLock.unlock();

		if (!type || !value)
			return errorDesc;

		aconnect::string_constptr format  = "Python: exception caught, type: %s\n"
			"Exception value: %s\n";

		str info (format % make_tuple ( handle<> (type), handle<> (value) ) );
		errorDesc = extract<aconnect::string> ( info );

		if (traceback && traceback != Py_None) {

			reference_existing_object::apply<TracebackLoaderWrapper*>::type converter;
			TracebackLoaderWrapper loader;

			handle<> loaderHandle ( converter( &loader ) );
			object tracebackLoader = object( loaderHandle );

			PyTraceBack_Print(traceback, loaderHandle.get() );
			errorDesc += extract<aconnect::string> ( str (tracebackLoader.attr ("content") ) );
			errorDesc += "\n";
		} 

	} catch (...) {
		errorDesc = "Python exception description loading failed";
	}

	return errorDesc;
}

